package KMeans;

import java.util.LinkedList;
import java.util.Random;

import javax.swing.JFrame;

/*
 * initialization: Random Partition
 * Distance Computing: ? 
 * 1 Euclidean distance 
 * 2 preprocessing before computing distance
 */
public class Kmeans {

	public static void main(String[] args) {
		Kmeans km = new Kmeans(2);
		 km.cluster(0);
		// km.dataset.show_label();
		 km.show_correctness();
//		 ShowColor sc = new ShowColor(km.centers.get(0));
		// System.out.println(km.distance_neighbor(km.centers.get(0),
		// km.centers.get(1)));

	}

	static Random rand;

	DataSet dataset;
	int np; // number of partition
	LinkedList<CB> centers;

	void cluster(int index) {

		boolean steady = false;
		do {

			System.out.println("****************************************");
			if (index == 0) {
				this.updateCenters_average();
			} else {
				this.updateCenters_neighbor();
			}
			// System.out.println(this.cluster_once());
			steady = this.cluster_once(index);
			// for (int i = 0; i < centers.size(); i++) {
			// centers.get(i).show();
			// /*
			// * show the color combination in a window
			// */
			// // ShowColor sc = new ShowColor(km.centers.get(i));
			// // sc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			// }
			System.out.println("****************************************");

		} while (!steady);

	}

	/*
	 * compute the correctness of the cluster: compare the tlabel of the color
	 * combination with its true label
	 */
	void show_correctness() {
		int tErro = 0;
		int fp = 0; // false positive
		int fn = 0; // false negative
		for (int i = 0; i < this.dataset.cbs.size(); i++) {
			if (this.dataset.cbs.get(i).getLabel() != this.dataset.cbs.get(i).tlabel) {
				tErro++;
				if (this.dataset.cbs.get(i).getLabel() == 0) {
					fn++;
				} else {
					fp++;
				}

			}

		}
		System.out.println(fn
				+ " out of 31 good combinations are classified as bad");
		System.out.println(fp + " out of " + (this.dataset.cbs.size() - 31)
				+ " randomly generated combinations are classified as good");
	}

	public Kmeans(int np) {
		// TODO Auto-generated constructor stub
		rand = new Random();
//		 rand.setSeed(2);

		dataset = new DataSet();
		// System.out.println("dataset size : " + dataset.cbs.size());
		this.np = np;
		this.RP(np);
		this.centers = new LinkedList<CB>();
		/*
		 * initialize the centers
		 */
		for (int i = 0; i < np; i++) {
			CB temp = new CB();
			for (int j = 0; j < CB.DEM; j++) {
				temp.addRegion(0, 0, 0);
			}
			this.centers.add(temp);
		}

	}

	/*
	 * update the centers to the mean of the partition
	 */
	void updateCenters_average() {

		int[] pnum = new int[np];

		/*
		 * reset the centers to all zeros
		 */
		for (int i = 0; i < np; i++) {
			for (int j = 0; j < CB.DEM; j++) {
				centers.get(i).Rlist.get(j).R = 0;
				centers.get(i).Rlist.get(j).G = 0;
				centers.get(i).Rlist.get(j).B = 0;
			}
		}

		for (int i = 0; i < dataset.cbs.size(); i++) {
			int index = this.dataset.cbs.get(i).tlabel;
			pnum[index]++;

			for (int j = 0; j < CB.DEM; j++) {
				this.centers.get(index).Rlist.get(j).R += this.dataset.cbs
						.get(i).Rlist.get(j).R;
				this.centers.get(index).Rlist.get(j).G += this.dataset.cbs
						.get(i).Rlist.get(j).G;
				this.centers.get(index).Rlist.get(j).B += this.dataset.cbs
						.get(i).Rlist.get(j).B;
			}

		}
		for (int i = 0; i < pnum.length; i++) {
			System.out.println("number of class " + i + " : " + pnum[i]);
		}

		for (int i = 0; i < np; i++) {
			for (int j = 0; j < CB.DEM; j++) {
				if (pnum[i] == 0) {
					this.centers.get(i).Rlist.get(j).R = 0;
					this.centers.get(i).Rlist.get(j).G = 0;
					this.centers.get(i).Rlist.get(j).B = 0;
				} else {
					this.centers.get(i).Rlist.get(j).R = this.centers.get(i).Rlist
							.get(j).R / pnum[i];
					this.centers.get(i).Rlist.get(j).G = this.centers.get(i).Rlist
							.get(j).G / pnum[i];
					this.centers.get(i).Rlist.get(j).B = this.centers.get(i).Rlist
							.get(j).B / pnum[i];
				}

			}
		}
	}

	/*
	 * update the tlabel of each color combination by the new K means and then
	 * update the k means return true if there is change of tlabel
	 */
	boolean cluster_once(int index) {
		int dis[] = new int[this.np];
		boolean steady = true;
		for (int i = 0; i < this.dataset.cbs.size(); i++) {
			for (int j = 0; j < this.centers.size(); j++) {
				if (index == 0) {
					dis[j] = this.distance_euclidean(centers.get(j),
							this.dataset.cbs.get(i));
				} else {

					dis[j] = this.distance_neighbor(centers.get(j),
							this.dataset.cbs.get(i));
				}

			}
			int min = dis[0];
			int tlabel = 0;
			for (int j = 1; j < dis.length; j++) {
				if (dis[j] <= min) {
					tlabel = j;
					min = dis[j];
				}
			}
			if (tlabel != dataset.cbs.get(i).tlabel) {
				dataset.cbs.get(i).tlabel = tlabel;
				steady = false;
			}
		}
		return steady;
	}

	int distance_euclidean(CB x, CB y) {// distance computing directly
		int dis = 0;
		for (int i = 0; i < CB.DEM; i++) {
			dis += Math.pow((x.Rlist.get(i).R - y.Rlist.get(i).R), 2);
			dis += Math.pow((x.Rlist.get(i).G - y.Rlist.get(i).G), 2);
			dis += Math.pow((x.Rlist.get(i).B - y.Rlist.get(i).B), 2);
		}
		return dis;
	}

	/*
	 * another version of computing distance preprocessing the color
	 * combination: compute the difference between right region use these
	 * difference to compute the distance between color combination
	 * 
	 * !! may need new version of updateCenters();
	 */
	int distance_neighbor(CB x, CB y) {// distance computing with
		int distance = 0;
		int dx = 0;
		int dy = 0;
		int nx[] = new int[CB.DEM - 1];
		int ny[] = new int[CB.DEM - 1];

		for (int i = 0; i < CB.DEM - 1; i++) {
			dx += Math.pow((x.Rlist.get(i).R - x.Rlist.get(i + 1).R), 2);
			dx += Math.pow((x.Rlist.get(i).G - x.Rlist.get(i + 1).G), 2);
			dx += Math.pow((x.Rlist.get(i).B - x.Rlist.get(i + 1).B), 2);
			nx[i] = dx;

			// nx[i] = (int) Math.sqrt(dx);

			dy += Math.pow((y.Rlist.get(i).R - y.Rlist.get(i + 1).R), 2);
			dy += Math.pow((y.Rlist.get(i).G - y.Rlist.get(i + 1).G), 2);
			dy += Math.pow((y.Rlist.get(i).B - y.Rlist.get(i + 1).B), 2);
			ny[i] = dx;

			// ny[i] = (int) Math.sqrt(dy);

			dx = dy = 0;

		}
		for (int i = 0; i < ny.length; i++) {
			distance += Math.pow(nx[i] - ny[i], 2);
		}

		// return (int) Math.sqrt(distance);
		return distance;
	}

	/*
	 * update Centers according to the average neighbor distance of the
	 * partition ? How about the leftmost color? average?
	 */
	void updateCenters_neighbor() {
		/*
		 * clean the centers
		 */
		this.cleanCenters();
		// the average neighbor distance
		int[][][] nd = new int[np][CB.DEM - 1][3];
		// number of instance of each partition
		int pnum[] = new int[np];

		for (int i = 0; i < this.dataset.cbs.size(); i++) {
			CB temp = this.dataset.cbs.get(i);
			pnum[temp.tlabel]++;
			int index = temp.tlabel;
			for (int j = 0; j < CB.DEM; j++) {
				centers.get(index).Rlist.get(j).R += temp.Rlist.get(j).R;
				centers.get(index).Rlist.get(j).G += temp.Rlist.get(j).G;
				centers.get(index).Rlist.get(j).B += temp.Rlist.get(j).B;
			}

			for (int j = 0; j < CB.DEM - 1; j++) {
				nd[index][j][0] += temp.nd[j][0];
				nd[index][j][1] += temp.nd[j][1];
				nd[index][j][2] += temp.nd[j][2];
			}

		}
		/*
		 * compute the average neighbor distance of each color
		 */
		for (int i = 0; i < pnum.length; i++) {
			System.out.println(pnum[i]);
		}
		for (int i = 0; i < pnum.length; i++) {
			for (int j = 0; j < CB.DEM - 1; j++) {
				nd[i][j][0] /= pnum[i];
				nd[i][j][1] /= pnum[i];
				nd[i][j][2] /= pnum[i];
			}
		}
		for (int i = 0; i < np; i++) {
			for (int j = 0; j < CB.DEM; j++) {
				if (pnum[i] == 0) {
					this.centers.get(i).Rlist.get(j).R = 0;
					this.centers.get(i).Rlist.get(j).G = 0;
					this.centers.get(i).Rlist.get(j).B = 0;
				} else {
					this.centers.get(i).Rlist.get(j).R = this.centers.get(i).Rlist
							.get(j).R / pnum[i];
					this.centers.get(i).Rlist.get(j).G = this.centers.get(i).Rlist
							.get(j).G / pnum[i];
					this.centers.get(i).Rlist.get(j).B = this.centers.get(i).Rlist
							.get(j).B / pnum[i];
				}

			}
		}

		/*
		 * adjust the color of each region except the first one
		 */
		for (int i = 0; i < pnum.length; i++) {
			for (int j = 1; j < CB.DEM; j++) {
				this.centers.get(i).Rlist.get(j).R += nd[i][j - 1][0];
				if (this.centers.get(i).Rlist.get(j).R < 0) {
					System.out.println("less than zero RGB value");
					this.centers.get(i).Rlist.get(j).R = 0;
				}
				this.centers.get(i).Rlist.get(j).G += nd[i][j - 1][1];
				if (this.centers.get(i).Rlist.get(j).G < 0) {
					System.out.println("less than zero RGB value");
					this.centers.get(i).Rlist.get(j).G = 0;
				}
				this.centers.get(i).Rlist.get(j).B += nd[i][j - 1][2];
				if (this.centers.get(i).Rlist.get(j).B < 0) {
					System.out.println("less than zero RGB value");
					this.centers.get(i).Rlist.get(j).B = 0;
				}
			}
		}

	}

	void cleanCenters() {
		for (int i = 0; i < np; i++) {
			for (int j = 0; j < CB.DEM; j++) {
				centers.get(i).Rlist.get(j).R = 0;
				centers.get(i).Rlist.get(j).G = 0;
				centers.get(i).Rlist.get(j).B = 0;
			}
		}
	}

	/*
	 * randomly label the tlabel of every color combination within the dataset;
	 * np : number of partition(num of seeds)
	 */
	void RP(int np) {// Random Partition

		for (int i = 0; i < this.dataset.cbs.size(); i++) {
			this.dataset.cbs.get(i).tlabel = Math.abs(this.rand.nextInt() % np);
		}
	}

}
